package org.NooLab.glue.net.ftp;



import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.util.Vector;

import org.NooLab.glue.net.DataSlot;
import org.NooLab.utilities.logging.PrintLog;
import org.apache.ftpserver.ftplet.DataConnection;
import org.apache.ftpserver.ftplet.DefaultFtpReply;
import org.apache.ftpserver.ftplet.DefaultFtplet;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpRequest;
import org.apache.ftpserver.ftplet.FtpSession;
import org.apache.ftpserver.ftplet.FtpletContext;
import org.apache.ftpserver.ftplet.FtpletResult;



/**
 * 
 * http://mina.apache.org/ftpserver/ftplet.html
 * 
 * http://svn.apache.org/viewvc/mina/ftpserver/branches/branch_1.4/core/src/java/org/apache/ftpserver/FtpServer.java?revision=661232&view=markup
 * 
 * response code at the end of the file
 * 
 * In Apache FtpServer you can intercept any command with an Ftplet, including LIST. 
 * Implement the Ftplet interface and override the beforeCommand or afterCommand 
 * (depending on if you want to handle the request or the response). 
 * 
 * Those methods will be called for every command sent to the server. But, from your question, you 
 * might instead be interested in instead implementing a custom file system. 
 * Look at the FileSystemFactory, FileSystemView and FtpFile interfaces.

 */
class GlueFTPlet extends DefaultFtplet 
						implements Runnable{ // so, Observable is not possible any more...

	FtpletContext ftpletContext  ;
	
	// 
	GlueFtpletHandlerIntf glueBinding;
	
	Thread gfThrd;
	boolean isRunning = false;
	boolean handlingNewBinding = false;
	
	int openConnectionsCount=0;
	
	/*
	 * streams are streams of data, where an interruption does NOT stop the connection;
	 * it relies on a keepAlivetimeOut
	 * streaming may be organized between two clients, then it requires incoming streams;
	 * a stream might also originate at side of the server, e.g. in case of a webcam,
	 * or after a database request
	 * 
	 * such streams could be relayed/directed to several receivers!
	 * 
	 */
	boolean ftpStreaming = false;
	int keepAlivetimeOut = 0 ;
	
	long globalTransmissionID = 0;
	
	Vector<DataSlot> dataSlots = new Vector<DataSlot>();
  	
	int printLevel = 2;
	PrintLog out ;

	
	
	public GlueFTPlet( GlueFtpletHandlerIntf gluebinding, int printlevel){

		glueBinding = gluebinding;
		
		printLevel = printlevel;
		if (printLevel>=1){
			out = new PrintLog(printLevel,true);
		}
			
		try {
			// Ftplet will get the Ftplet specific configuration as the init() method argument.
			// thats an interface... ???
			// this.init(ftpletContext);
			
		// } catch (FtpException e) {
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		// starting the thread which creates hander objects
		gfThrd = new Thread(this,"glue binding");
		gfThrd.start() ;
	}

	public void decr(int count){
		
		if (openConnectionsCount>1){
			openConnectionsCount--;
		}else{
			openConnectionsCount=0;
		}
									out.print(4,"- open connections : " +openConnectionsCount);
	}
	
	public void incr(int count){
		openConnectionsCount++;
									out.print(4,"- open connections : " +openConnectionsCount);
	}
	

	public void run() {
		isRunning = true ;
		
		DataSlot dataSlot;
		boolean isProcessing=false;
		
		
		
		try{
			

			while (isRunning){
				
				if (dataSlots.size()>0){
					handlingNewBinding=true ;
					
					if (isProcessing==false){
						isProcessing=true;
						
						 
						// call handling routines = provide data
						 
						dataSlot = dataSlots.get(0);
						// TODO make option for it 
						// str = new String(dataSlot.byteArray);
						// glueBinding.getFtpMessageStr(str);
						
										out.print(3, "ftplet, providing data ");
						glueBinding.getFtpMessageObj(dataSlot) ;
						
						// remove obj from list, destroy the object
						dataSlots.remove(0);
						 
						isProcessing=false;
					}
				}// handlingNewBinding
			}
		}catch(Exception e){
			
		}
		
	}
	
	// ========================================================================
	
	/**
	 * 
	 * catching the event, creating a handler class, and skipping the further processing 
	 * by the ftpserver
	 */
	public FtpletResult onUploadStart(  FtpSession ftpSession, 
										FtpRequest ftpRequest) throws FtpException, IOException {
		
		SessionAcceptor sac;
		FtpletResult result ;
		
		InetSocketAddress sockaddr;
		InetAddress addr ;
		UUID sid1 ;
		int cliPort,srvPort ;
		
		// ................................................
		
		sockaddr = ftpSession.getClientAddress() ;
		addr = sockaddr.getAddress() ;
		  
		 
		// boolean ... addr.isSiteLocalAddress();

		sid1 = ftpSession.getSessionId();
		cliPort = sockaddr.getPort();
		srvPort = ftpSession.getServerAddress().getPort() ;
		
												out.print(3, "onUploadStart() -> "+ftpRequest.getRequestLine() );
												out.print(3, "connection ("+sid1+"): "+addr+"  to port "+srvPort+" from remote port "+cliPort);	  
		
		result = FtpletResult.SKIP;
		
		/*
		    we open a thread for each incoming session, so even the retrieval of data from the
		    connection may run concurrently in a nicely boxed manner 
		    
		    this mechanism with a buffering collection should be very fast,
		   	so the capacity should be very high, 
		*/
		
		try {
			
			if (ftpStreaming==false){
				// no streaming, simple data object, simple close
				sac = new SessionAcceptor(ftpSession) ; 
			} else{
				// we keep the session open, routing the stream directly to the consumer,
				// using a specialized interface
				// the session will be closed only after an appropriate long timeout
				// keepAlivetimeOut
				
			}

		 
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return result ;
	}

		
	// this version of interception relays the uploaded file, and no physical file will be written
	// at the ftpserver's download location (due to result SKIP)
	
	// ========================================================================
	
	public void onDownloadEnd(){
	
		out.print(2, "arrived @ onDownloadEnd()");
		
		
	}


	// this version of interception relays the uploaded file, and no physical file will be written
	// at the ftpserver's download location (due to result SKIP)
	
	// ========================================================================
	
 

	public FtpletResult onDisconnect(FtpSession ftpSession){
		
		FtpletResult result = FtpletResult.DEFAULT ;
		
		decr(openConnectionsCount);
		

		
		return result ;
	}
	
	public FtpletResult onConnect(FtpSession ftpSession){
		FtpletResult result = FtpletResult.DEFAULT ;
		
		incr(openConnectionsCount);
		return result ;
	}

	
	
	/*
	 * this is an example for proxy like routing
	 */
	@SuppressWarnings("unused")
	private FtpletResult onUploadStart_(  FtpSession ftpSession, 
										FtpRequest ftpRequest) throws FtpException, IOException {
		
		
		File tempfile ;
		DataConnection dataConnection ;
		
		tempfile = File.createTempFile("upload", "ftp");
		
		OutputStream stream = new FileOutputStream(tempfile);
	
		try {
			ftpSession.write(new DefaultFtpReply(150, "File status okay; about to open data connection."));
			
			dataConnection = ftpSession.getDataConnection().openConnection();
			
			dataConnection.transferFromClient(ftpSession, stream);
			
			stream.flush();
			stream.close();
			ftpSession.getDataConnection().closeDataConnection();
			ftpSession.write(new DefaultFtpReply(226, "Closing data connection. Requested file action successful"));
			
		} catch (Exception e) {
			// logger.error("Could not upload file", e);
			out.print( 1, "Error in event 'onUploadStart()', could not upload file\n"+
						   e.getLocalizedMessage() + "\n" +
						   e.getStackTrace());
		}
		return FtpletResult.SKIP;
	}

 

	protected void readingDataFromSessionToFiFo( FtpSession ftpSession, long transmissionid){
		
		
		InetSocketAddress sockaddr;
		InetAddress addr ;
		 
		
		
		DataSlot dataSlot;
		
		try{

			dataSlot = new DataSlot();

			dataSlot.transmissionID = transmissionid;
			
			ftpSession.write(new DefaultFtpReply(150, "File status okay; about to open data connection."));

			
			sockaddr = ftpSession.getClientAddress() ;
			addr = sockaddr.getAddress() ;
			
			dataSlot.serverPort = ftpSession.getServerAddress().getPort() ;			
			dataSlot.clientPort = sockaddr.getPort();
			dataSlot.clientAddr = addr.getHostAddress().toString() ;
			
			dataSlot.dataConnection = ftpSession.getDataConnection().openConnection();
			dataSlot.dataConnection.transferFromClient(ftpSession,dataSlot.byteOutStream);
			dataSlot.closeStream();

			ftpSession.getDataConnection().closeDataConnection();
			
			ftpSession.write(new DefaultFtpReply(226,"Closing data connection. Requested action was successfully finished."));
			
			dataSlot.sessionID = ftpSession.getSessionId();
			dataSlots.add(dataSlot);

		}catch(Exception e){
			
		}
		
	}
	
	synchronized protected long inc( long transmissionID ){
		
		globalTransmissionID = transmissionID++; 
		return globalTransmissionID;
	}
	
	class SessionAcceptor implements Runnable{
		
		FtpSession ftpSession;
		Thread sacThrd ;
		long transmissionID;
		
		
		public SessionAcceptor( FtpSession ftpsession ){
			
			ftpSession = ftpsession ;
													out.print(4, "ftplet, starting data handling thread... ");
			sacThrd = new Thread(this, "SessionAcceptor :: "+ftpsession.getSessionId().toString());
			sacThrd.start();
		}

		@Override
		public void run() {
			
			try {
				
				transmissionID = inc(globalTransmissionID);
				
				readingDataFromSessionToFiFo(ftpSession,transmissionID);

				sacThrd.join();

			} catch (InterruptedException e) {
			}
		}
		
	}
	public int getPrintLevel() {
		return printLevel;
	}


	public void setPrintLevel(int printLevel) {
		this.printLevel = printLevel;
		out = new PrintLog(printLevel,true);
	}

}


/*

Response Codes

This section gives an overview on different response codes which might be useful in developing custom ftplet. These commands might be used when the ftplet method return value is SKIP.
Method 	Response Code 	Description
onConnect 	220 	Service ready for new user.
onConnect 	530 	No server access from the IP.
onConnect 	530 	Maximum server connection has been reached.
onDisconnect 	
	
onLogin 	
	
onDeleteStart 	250 	Requested file action okay
onDeleteStart 	450 	No permission to delete.
onDeleteStart 	550 	Not a valid file.
onDeleteStart 	450 	Can't delete file.
onDeleteEnd 	
	
onUploadStart 	150 	File status okay; about to open data connection.
onUploadStart 	226 	Transfer complete.
onUploadStart 	550 	Invalid path.
onUploadStart 	550 	Permission denied.
onUploadStart 	425 	Can't open data connection.
onUploadStart 	426 	Data connection error.
onUploadStart 	551 	Error on output file.
onUploadEnd 	
	
onDownloadStart 	150 	File status okay; about to open data connection.
onDownloadStart 	226 	Transfer complete.
onDownloadStart 	550 	No such file or directory.
onDownloadStart 	550 	Not a plain file.
onDownloadStart 	550 	Permission denied.
onDownloadStart 	425 	Can't open data connection.
onDownloadStart 	426 	Data connection error.
onDownloadStart 	551 	Error on input file.
onDownloadEnd 	
	
onRmdirStart 	250 	Directory removed.
onRmdirStart 	550 	Not a valid directory.
onRmdirStart 	550 	Permission denied.
onRmdirStart 	550 	Can't remove directory.
onRmdirEnd 	
	
onMkdirStart 	250 	Directory created.
onMkdirStart 	550 	Not a valid file.
onMkdirStart 	550 	Permission denied
onMkdirStart 	550 	Directory already exists.
onMkdirStart 	550 	Can't create directory.
onMkdirEnd 	
	
onAppendStart 	150 	File status okay; about to open data connection.
onAppendStart 	226 	Transfer complete.
onAppendStart 	550 	Not a plain file.
onAppendStart 	550 	Permission denied.
onAppendStart 	425 	Can't open data connection.
onAppendStart 	426 	Data connection error.
onAppendStart 	551 	Error on output file.
onAppendEnd 	
	
onUploadUniqueStart 	150 	File status okay; about to open data connection.
onUploadUniqueStart 	250 	filename: Transfer started.
onUploadUniqueStart 	226 	filename: Transfer complete.
onUploadUniqueStart 	550 	Unique file name error.
onUploadUniqueStart 	550 	Permission denied.
onUploadUniqueStart 	425 	Can't open data connection.
onUploadUniqueStart 	426 	Data connection error.
onUploadUniqueStart 	551 	Error on output file.
onUploadUniqueEnd 	
	
onRenameStart 	250 	Requested file action okay, file renamed.
onRenameStart 	503 	Can't find the file which has to be renamed.
onRenameStart 	553 	Not a valid file name.
onRenameStart 	553 	Permission denied.
onRenameStart 	553 	No such file or directory.
onRenameStart 	553 	Can't rename file.
onRenameEnd 	
	
onSite 	200 	Command SITE okay.
onSite 	530 	Permission denied.
onSite 	502 	Not implemented.
onSite 	500 	Execution failed. 


*/